package org.traffometer.traffosim;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.traffometer.common.data.TraffoPacket;
import org.traffometer.traffosim.Config.ConfigType;
import org.traffometer.traffosim.Config.TransmissionMode;

/**
 * Writes a list of packets to an XML file using the Traffopacket XML file format.
 * 
 * @author George Lee
 * 
 */
public class ConfigWriter {

  /** List containing the packets to be written. */
  protected List<TraffoPacket> packetList = new ArrayList<TraffoPacket>();

  /** Type of configuration file. */
  private ConfigType configType;

  /** Transmission mode of the configuration file. */
  private TransmissionMode transMode;

  /** Rate at which to send packets (when using the batch transmission mode). */
  private int batchRate;

  /** Sets batch rate to "auto". */
  private static final int AUTO_RATE = 0;

  /**
   * XML header for files.
   */
  private static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
  /**
   * Name of root XML element for configuration files.
   */
  private static final String ROOT_ELEMENT = "config";
  /**
   * XML attribute name for the type of a configuration.
   */
  private static final String TYPE_ATTRIBUTE = "type";
  /**
   * XML element name containing the dynamic parameters.
   */
  private static final String DYNAMIC_PARAMETERS_ELEMENT = "dynamic-parameters";
  /**
   * XML element name containing the transmission mode.
   */
  private static final String TRANSMISSION_MODE_ELEMENT = "transmission-mode";
  /**
   * XML attribute name for the transmission mode of a configuration.
   */
  private static final String MODE_ATTRIBUTE = "mode";
  /**
   * XML attribute name for the rate of transmission for batch transmission mode.
   */
  private static final String RATE_ATTRIBUTE = "rate";
  /**
   * XML element name containing the list of TraffoPackets.
   */
  private static final String DATA_ELEMENT = "data";
  /**
   * XML element name containing the data for a TraffoPacket.
   */
  private static final String TRAFFOPACKET_ELEMENT = "traffopacket";
  /**
   * XML attribute name for latitude of the first point in a traffopacket.
   */
  private static final String LAT_1_ATTRIBUTE = "lat-1";
  /**
   * XML attribute name for longitude of the first point in a traffopacket.
   */
  private static final String LNG_1_ATTRIBUTE = "lng-1";
  /**
   * XML attribute name for time of the first point in a traffopacket.
   */
  private static final String TIME_1_ATTRIBUTE = "time-1";
  /**
   * XML attribute name for latitude of the first point in a traffopacket.
   */
  private static final String LAT_2_ATTRIBUTE = "lat-2";
  /**
   * XML attribute name for longitude of the first point in a traffopacket.
   */
  private static final String LNG_2_ATTRIBUTE = "lng-2";
  /**
   * XML attribute name for time of the first point in a traffopacket.
   */
  private static final String TIME_2_ATTRIBUTE = "time-2";
  /**
   * XML attribute name for time of the first point in a traffopacket.
   */
  private static final String VEHICLE_TYPE_ATTRIBUTE = "vehicle";

  /**
   * Create a ConfigWriter that uses the given configuration type.
   * 
   * @param configType The configuration type of the file.
   * @param transMode The transmission mode of the file.
   * @throws ConfigWriterException thrown when invalid parameters are passed.
   */
  public ConfigWriter(String configType, String transMode) throws ConfigWriterException {
    // Set the configuration type.
    if (configType.equalsIgnoreCase("static")) {
      this.configType = ConfigType.STATIC;
    }

    else if (configType.equalsIgnoreCase("dynamic")) {
      this.configType = ConfigType.DYNAMIC;
    }

    else {
      throw new ConfigWriterException("Invalid configuration type.");
    }

    // Set the transmission mode.
    if (transMode.equalsIgnoreCase("real-time")) {
      this.transMode = TransmissionMode.REAL_TIME;
    }

    else if (transMode.equalsIgnoreCase("batch")) {
      this.transMode = TransmissionMode.BATCH;
    }

    else if (transMode.equalsIgnoreCase("load-test")) {
      this.transMode = TransmissionMode.LOAD_TEST;
    }

    else {
      throw new ConfigWriterException("Invalid transmission mode.");
    }
  }

  /**
   * Create a ConfigWriter that uses the given config type and transmission mode. Also takes the
   * batch rate if the transmission mode is batch.
   * 
   * @param configType The configuration type of the file.
   * @param transMode The transmission mode of the file.
   * @param rate The rate of transmission for each packet (if the transmission mode is batch).
   * @throws ConfigWriterException thrown when invalid parameters are passed.
   */
  public ConfigWriter(String configType, String transMode, int rate) throws ConfigWriterException {
    this(configType, transMode);

    // If rate is an invalid value, set to auto batch rate.
    if (rate < 0) {
      this.batchRate = AUTO_RATE;
    }

    else {
      this.batchRate = rate;
    }
  }

  /**
   * Get the configuration type.
   * 
   * @return Returns either "dynamic" or "static" depending on the configuration type.
   */
  public String getConfigType() {
    return this.configType.typeName();
  }

  /**
   * Set the configuration type.
   * 
   * @param configType The new configuration type of this config writer.
   * @throws ConfigWriterException thrown when an invalid config type is specified.
   */
  public void setConfigType(String configType) throws ConfigWriterException {
    // Set the configuration type.
    if (configType.equalsIgnoreCase("static")) {
      this.configType = ConfigType.STATIC;
    }

    else if (configType.equalsIgnoreCase("dynamic")) {
      this.configType = ConfigType.DYNAMIC;
    }

    else {
      throw new ConfigWriterException("Invalid configuration type.");
    }
  }

  /**
   * Get the transmission mode.
   * 
   * @return Returns real-time, batch, or load-test depending on the transmission mode of the
   *         configuration writer.
   */
  public String getTransmissionMode() {
    return this.transMode.elementName();
  }

  /**
   * Set the transmission mode.
   * 
   * @param transMode The new transmission mode of this config writer.
   * @throws ConfigWriterException thrown when an invalid transmission mode is specified.
   */
  public void setTransmissionMode(String transMode) throws ConfigWriterException {
    // Set the transmission mode.
    if (transMode.equalsIgnoreCase("real-time")) {
      this.transMode = TransmissionMode.REAL_TIME;
    }

    else if (transMode.equalsIgnoreCase("batch")) {
      this.transMode = TransmissionMode.BATCH;
    }

    else if (transMode.equalsIgnoreCase("load-test")) {
      this.transMode = TransmissionMode.LOAD_TEST;
    }

    else {
      throw new ConfigWriterException("Invalid transmission mode.");
    }
  }

  /**
   * Get the batch transmission rate.
   * 
   * @return The rate at which packets will be transmitted when using the batch transmission type.
   */
  public int getBatchRate() {
    return this.batchRate;
  }

  /**
   * Set the batch transmission rate.
   * 
   * @param transRate The new transmission rate for the configuration writer (when using the batch
   *        transmission mode).
   */
  public void setBatchRate(int transRate) {
    if (transRate <= 0) {
      this.batchRate = AUTO_RATE;
    }

    else {
      this.batchRate = transRate;
    }
  }

  /**
   * Get the TraffoPacket at the specified index.
   * 
   * @param index The index of the TraffoPacket to retrieve.
   * @return The TraffoPacket located at the index.
   */
  public TraffoPacket getPacket(int index) {
    return this.packetList.get(index);
  }

  /**
   * Add the TraffoPacket to the list.
   * 
   * @param packet The TraffoPacket to be added to the list.
   */
  public void addPacket(TraffoPacket packet) {
    this.packetList.add(packet);
  }

  /**
   * Remove the TraffoPacket at the specified index.
   * 
   * @param index The index of the TraffoPacket to be deleted.
   */
  public void removePacket(int index) {
    this.packetList.remove(index);
  }
  
  /**
   * Get the number of packets in the list.
   * 
   * @return The number of packets stored by the ConfigWriter.
   */
  public int getNumPackets() {
    return this.packetList.size();
  }

  /**
   * Save the configuration file to the specified path.
   * 
   * @param filePath The path to write the configuration file to.
   * @throws ConfigWriterException when there is an error creating the configuration file.
   */
  public void save(String filePath) throws ConfigWriterException {
    // Create the appropriate configuration type.
    if (this.configType == ConfigType.STATIC) {
      saveStatic(filePath);
    }

    else {
      saveDynamic(filePath);
    }
  }

  /**
   * Save a static configuration file to the specified path.
   * 
   * @param filePath The path to write the configuration file to.
   * @throws ConfigWriterException when there is an error in creating the file.
   */
  @SuppressWarnings("AvoidDuplicateLiterals")
  private void saveStatic(String filePath) throws ConfigWriterException {
    // Create a file writer that writes to the specified file path.
    BufferedWriter bout;
    try {
      bout = new BufferedWriter(new FileWriter(filePath));
    }

    catch (IOException e) {
      throw new ConfigWriterException(e, "Error in creating output file.");
    }

    // Write xml header and configuration type.
    try {
      bout.write(XML_HEADER);
      bout.newLine();
      bout.write("<" + ROOT_ELEMENT + " " + TYPE_ATTRIBUTE + "=\"" + getConfigType() + "\">");
      bout.newLine();
    }

    catch (IOException e) {
      throw new ConfigWriterException(e, "Error writing the configuration type of the file.");
    }

    // Write transmission mode and necessary parameters.
    try {
      bout.write("<" + TRANSMISSION_MODE_ELEMENT + " " + MODE_ATTRIBUTE + "=\""
          + getTransmissionMode() + "\">");
      bout.newLine();
      
      //Write batch transmission rate.
      if (getTransmissionMode().equalsIgnoreCase("batch")) {
        if (getBatchRate() == AUTO_RATE) {
          bout.write("<batch " + RATE_ATTRIBUTE + "=\"auto\" />");
          bout.newLine();
        }
        else {
          bout.write("<batch " + RATE_ATTRIBUTE + "=\"" + getBatchRate() + "\" />");
          bout.newLine();
        }
      }

      // Add support for other transmission modes.
      else {
        throw new ConfigWriterException("Other transmission modes not yet implemented.");
      }

      // Close transmission mode element.
      bout.write("</" + TRANSMISSION_MODE_ELEMENT + ">");
      bout.newLine();
    }

    catch (IOException e) {
      throw new ConfigWriterException(e, "Error writing the transmission mode.");
    }
    
    // Write packet data.
    try {
      bout.write("<" + DATA_ELEMENT + ">");
      bout.newLine();
      
      //Iterate through list and write the data.
      for (TraffoPacket element : this.packetList) {
        bout.write("<" + TRAFFOPACKET_ELEMENT + " ");
        bout.write(LAT_1_ATTRIBUTE + "=\"" + element.getLatitude1() + "\" ");
        bout.write(LNG_1_ATTRIBUTE + "=\"" + element.getLongitude1() + "\" ");
        bout.write(TIME_1_ATTRIBUTE + "=\"" + element.getTimeStamp1() + "\" ");
        bout.write(LAT_2_ATTRIBUTE + "=\"" + element.getLatitude2() + "\" ");
        bout.write(LNG_2_ATTRIBUTE + "=\"" + element.getLongitude2() + "\" ");
        bout.write(TIME_2_ATTRIBUTE + "=\"" + element.getTimeStamp2() + "\" ");
        bout.write(VEHICLE_TYPE_ATTRIBUTE + "=\"" + element.getVehicleType() + "\" />");
        bout.newLine();
      }
      
      bout.write("</" + DATA_ELEMENT + ">");
      bout.newLine();
      
      // Close config tag.
      bout.write("</" + ROOT_ELEMENT + ">");
      
      //Close output stream.
      bout.close();
    }
    
    catch (IOException e) {
      throw new ConfigWriterException(e, "Error writing the packet data.");
    }
  }

  /**
   * Save a dynamic configuration file to the specified path.
   * 
   * @param filePath The path to write the configuration file to.
   * @throws ConfigWriterException when there is an error creating the file.
   */
  private void saveDynamic(String filePath) throws ConfigWriterException {
    throw new ConfigWriterException("Dynamic config file creation is not implemented yet.");
  }
}
